package com.wzw.expression.expression.invocation;

import com.wzw.expression.expression.operation.Operation;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.lang.NonNull;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Wangzhiwen
 */
public class OperationInvocationCache implements BeanFactoryAware {

    protected BeanFactory beanFactory;

    private final Map<Class<? extends Operation>, OperationInvocationMetadataProvider> operationMetadataProviderMap = new ConcurrentHashMap<>(16);

    private final Map<OperationInvocationCacheKey, OperationInvocationMetadata> metadataCache = new ConcurrentHashMap<>(1024);

    private final Map<Class<? extends Operation>, OperationInvocationContextProvider> operationContextProviderMap = new ConcurrentHashMap<>(16);

    public void addOperationMetadataProvider(OperationInvocationMetadataProvider provider) {
        operationMetadataProviderMap.put(provider.getType(), provider);
    }

    public void addOperationContextProvider(OperationInvocationContextProvider provider) {
        operationContextProviderMap.put(provider.getType(), provider);
    }

    public OperationInvocationContexts getOperationContexts(Collection<Operation> operations, Method method, Class<?> targetClass, Object[] args, Object target) {
        OperationInvocationContexts contexts = new OperationInvocationContexts();
        if (Objects.isNull(operations)) {
            return contexts;
        }
        operations.forEach(v -> contexts.addContext(getOperationInvocationContext(v, method, targetClass, args, target)));
        return contexts;
    }

    protected OperationInvocationContext getOperationInvocationContext(Operation operation, Method method, Class<?> targetClass, Object[] args, Object target) {
        // 先获取元数据对象
        OperationInvocationMetadata metadata = getOperationMetadata(operation, method, targetClass);
        if (!operationContextProviderMap.containsKey(operation.getClass())) {
            return EmptyOperationInvocationContext.INSTANCE;
        }
        OperationInvocationContextProvider provider = operationContextProviderMap.get(operation.getClass());
        return provider.get(metadata, args, target);
    }

    protected OperationInvocationMetadata getOperationMetadata(Operation operation, Method method, Class<?> targetClass) {
        OperationInvocationCacheKey cacheKey = new OperationInvocationCacheKey(operation, method, targetClass);
        OperationInvocationMetadata metadata = this.metadataCache.get(cacheKey);
        if (Objects.nonNull(metadata)) {
            return metadata;
        }
        // 写缓存
        if (!operationMetadataProviderMap.containsKey(operation.getClass())) {
            return EmptyOperationInvocationMetadata.INSTANCE;
        }
        OperationInvocationMetadataProvider provider = operationMetadataProviderMap.get(operation.getClass());
        OperationInvocationMetadata operationInvocationMetadata = provider.get(operation, method, targetClass, beanFactory);
        metadataCache.put(cacheKey, operationInvocationMetadata);
        return operationInvocationMetadata;
    }

    @Override
    public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}
